import 'dart:collection';

import 'sqliteasy_database.dart';

class MigrationContainer {
  final HashMap<int, SplayTreeMap<int, Migration>> _migrations = new HashMap();

  /**
   * Adds the given migrations to the list of available migrations. If 2 migrations have the
   * same start-end versions, the latter migration overrides the previous one.
   *
   * @param migrations List of available migrations.
   */
  void addMigrations(List<Migration> migrations) {
    for (Migration migration in migrations) {
      addMigration(migration);
    }
  }

  void addMigration(Migration migration) {
    final int start = migration.startVersion;
    final int end = migration.endVersion;
    SplayTreeMap<int, Migration> targetMap = _migrations[start];
    if (targetMap == null) {
      targetMap = SplayTreeMap();
      _migrations[start] = targetMap;
    }
    Migration existing = targetMap[end];
    if (existing != null) {
      print("[WARN] Overriding migration ${existing} with ${migration}");
    }
    targetMap[end] = migration;
  }

  /**
   * Finds the list of migrations that should be run to move from {@code start} version to
   * {@code end} version.
   *
   * @param start The current database version
   * @param end   The target database version
   * @return An ordered list of {@link Migration} objects that should be run to migrate
   * between the given versions. If a migration path cannot be found, returns {@code null}.
   */
  List<Migration> findMigrationPath(int start, int end) {
    if (start == end) {
      return [];
    }
    bool migrateUp = end > start;
    List<Migration> result = List();
    return findUpMigrationPath(result, migrateUp, start, end);
  }

  List<Migration> findUpMigrationPath(List<Migration> result, bool upgrade, int start, int end) {
    while (upgrade ? start < end : start > end) {
      SplayTreeMap<int, Migration> targetNodes = _migrations[start];
      if (targetNodes == null) {
        return null;
      }
      // keys are ordered so we can start searching from one end of them.
      Iterable<int> keySet;
      if (upgrade) {
        keySet = targetNodes.keys.toList().reversed;
      } else {
        keySet = targetNodes.keys;
      }
      bool found = false;
      for (int targetVersion in keySet) {
        bool shouldAddToPath;
        if (upgrade) {
          shouldAddToPath = targetVersion <= end && targetVersion > start;
        } else {
          shouldAddToPath = targetVersion >= end && targetVersion < start;
        }
        if (shouldAddToPath) {
          result.add(targetNodes[targetVersion]);
          start = targetVersion;
          found = true;
          break;
        }
      }
      if (!found) {
        return null;
      }
    }
    return result;
  }
}

typedef MigrationFunc = void Function(SupportSQLiteDatabase database);

class Migration {
  final int startVersion;
  final int endVersion;
  final MigrationFunc migrationFunc;

  Migration(this.startVersion, this.endVersion, this.migrationFunc)
      : assert(startVersion != null),
        assert(endVersion != null),
        assert(startVersion != endVersion),
        assert(migrationFunc != null);

  void migrate(SupportSQLiteDatabase database) {
    this.migrationFunc(database);
  }
}
